package de.htwg_konstanz.infen.aufg1.main;

import java.util.ArrayList;
import java.util.List;

import de.htwg_konstanz.infen.aufg1.inputXML.jaxbGenerated.Attribute;
import de.htwg_konstanz.infen.aufg1.inputXML.jaxbGenerated.Endpoint;
import de.htwg_konstanz.infen.aufg1.inputXML.jaxbGenerated.Endpoints;
import de.htwg_konstanz.infen.aufg1.inputXML.jaxbGenerated.RelationshipType;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TAlternateKeySet;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TAttributeSet;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TConfiguration;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TFd;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TFds;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TForeignKey;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TForeignKeys;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TTable;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TTableContainer;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TTableInfo;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TTypedAttribute;
import de.htwg_konstanz.infen.aufg1.outputXML.jaxbGenerated.TTypedAttributeSet;

/**
 * Date: 29.11.2010
 * Course: Information Engineering
 * Team: Valentin Durst, Tobias Kutter and Stefan Bernhard ...
 * 
 * Description:
 * The OutputHelper class creates the output XML structure, it supports the MainClass with
 * all needed methods to build the new XML file in a very comfortable and clear way.
 **/

public class OutputHelper 
{
	public TConfiguration theConfiguration = new TConfiguration();
	private TTableContainer aTableContainer = new TTableContainer();
	private TTableInfo rootElementTInfo = new TTableInfo();
	private InputInfos ii;
	
	public InputInfos getIi() {
		return this.ii;
	}
	public void setIi(InputInfos ii) {
		this.ii = ii;
	}
	public OutputHelper(InputInfos iis)
	{
		ii = iis;
		aTableContainer.setTableInfo(rootElementTInfo);
		theConfiguration.setSchema(aTableContainer);
	}
	
	public void addTable(String name,List<Attribute> atr, List<Attribute> primKey,List<Attribute> altKeys)
	{
		TTable ttable = new TTable();
		ttable.setName(name);
		rootElementTInfo.getTable().add(ttable);
		
		TAttributeSet tatset = new TAttributeSet();

		for(Attribute at : primKey)
		{
			tatset.getAttribute().add(at.getName());
		}

		ttable.setPrimaryKey(tatset);
		if(atr.size() != 0)
		{
			TTypedAttributeSet ttypedAttrSet = new TTypedAttributeSet();
			
			for(Attribute at : atr)
			{
				if(at != null)
				{
					try{
						TTypedAttribute ttypedAttr = new TTypedAttribute();
						ttypedAttr.setNotNull(at.isIsRequired());
						ttypedAttr.setValue(at.getName());
						ttypedAttrSet.getAttribute().add(ttypedAttr);
					}
					catch (NullPointerException e) {
					}
				}
			}
			ttable.setAttributes(ttypedAttrSet);		
		}
		if(altKeys != null)
		{
			if(altKeys.size() != 0)
			{
				TAlternateKeySet taks = new TAlternateKeySet();
				TAttributeSet tas = new TAttributeSet();
				for(Attribute at : altKeys)
				{
						tas.getAttribute().add(at.getName());
				}
				taks.getAlternateKey().add(tas);
				ttable.setAlternateKeys(taks);
			}
		}
	}
	
	public void addAttrToTable(String tabName,String refTabName, String attr)
	{	
		Attribute at = ii.getAttibute(refTabName, attr);
		for(TTable tab : rootElementTInfo.getTable()){
			if(tab.getName().equals(tabName))
			{
				TTypedAttribute tattr = new TTypedAttribute();
				try{
					tattr.setNotNull(at.isIsRequired());
				}catch(Exception e){}
				try{
					tattr.setDataType(at.getType());
				}catch(Exception e){}
				tattr.setValue(refTabName+"_"+at.getName());
				TTypedAttributeSet attrs = tab.getAttributes();
				if(attrs != null)
					tab.getAttributes().getAttribute().add(tattr);
				else{
					attrs = new TTypedAttributeSet();
					attrs.getAttribute().add(tattr);
					tab.setAttributes(attrs);
				}
				return;
			}
		}
	}
	
	// Add a Foreign key to an table
	public void addFKToTable2(String tabName, String refTabName, List<String> refTabAttribute)
	{	
		List<Attribute> atList = new ArrayList<Attribute>();
		for(String str : refTabAttribute){
			Attribute at = new Attribute();
			at.setName(str);
			atList.add(at);
		}
		addFKToTable(tabName,refTabName,atList,false);
	}
	
	// Add a Foreign key to an table
	public void addFKToTable(String tabName, String refTabName, List<Attribute> refTabAttribute,boolean addToAttrs)
	{
		TForeignKeys tforKeys;// = new TForeignKeys();
		TForeignKey tforKey = new TForeignKey();
		TAttributeSet tAttrSet = new TAttributeSet();
		TAttributeSet tAttrSet2 = new TAttributeSet();
		

		for(Attribute attr : refTabAttribute)
		{
			tAttrSet2.getAttribute().add(attr.getName());
		}
		tforKey.setReferencedAttributes(tAttrSet2);
		tforKey.setReferencedTable(refTabName);
		
		tforKeys = getTable(tabName).getForeignKeys();
		if(tforKeys == null)
			tforKeys = new TForeignKeys();
		tforKeys.getForeignKey().add(tforKey);
		
		for(TTable ttab : rootElementTInfo.getTable())
		{
			String tableName = ttab.getName();
			if(tableName.equals(tabName))
			{
				ttab.setForeignKeys(tforKeys);
			}
		}
		
		for(Attribute at : refTabAttribute)
		{
			if(addToAttrs)
			{	addAttrToTable(tabName, refTabName, at.getName());
			}
				tAttrSet.getAttribute().add(refTabName+"_"+at.getName()); 
		}
		tforKey.setAttributes( tAttrSet );
	}
	
	// Automatically creates all functional dependences from the Primary and Alternative key
	public void addFD_auto()
	{
		for(TTable ttab : rootElementTInfo.getTable())
		{
			List<String> pks = ttab.getPrimaryKey().getAttribute();
			List<Attribute> pkAttrs = new ArrayList<Attribute>();
			for(String name : pks)
			{
				Attribute aAttr = new Attribute();
				aAttr.setName(name);
				pkAttrs.add(aAttr);
			}
			TAlternateKeySet taks= ttab.getAlternateKeys();
			List<Attribute> akAttrs = new ArrayList<Attribute>();
			if(taks != null){
				List<TAttributeSet> aks = taks.getAlternateKey();
				if(aks != null)
				{
					for(TAttributeSet nameAtrSet : aks)
					{
						for(String name : nameAtrSet.getAttribute())
						{
							Attribute aAttr = new Attribute();
							aAttr.setName(name);
							akAttrs.add(aAttr);					
						}
					}
				}
			}
			
			List<TTypedAttribute> attrs = ttab.getAttributes().getAttribute();
			List<Attribute> theAttrs = new ArrayList<Attribute>();
			for(TTypedAttribute name : attrs)
			{
				Attribute aAttr = new Attribute();
				aAttr.setName(name.getValue());
				theAttrs.add(aAttr);
			}			
			addFD(ttab.getName(),pkAttrs,theAttrs);
			if(akAttrs != null)
				if(akAttrs.size() > 0)
					addFD(ttab.getName(),akAttrs,theAttrs);
		}
	}
	
	// add a functional dependency to an Table
	public void addFD(String entity,List<Attribute> latr, List<Attribute> ratr)
	{
		
		for(TTable ttab : rootElementTInfo.getTable())
		{
			String tableName = ttab.getName();
			if(tableName.equals(entity))
			{
				TFd tfd = new TFd();
				TAttributeSet tats1 = new TAttributeSet();
				TAttributeSet tats2 = new TAttributeSet();
				for(Attribute lat : latr)
				{	
					tats1.getAttribute().add(lat.getName());
					tfd.setLhs(tats1);
				}
				for(Attribute rat : ratr)
				{
					tats2.getAttribute().add(rat.getName());
					tfd.setRhs(tats2);
				}
				TFds tfds = new TFds();
				tfds.getFd().add(tfd);
				if(ttab.getFds() != null)
					ttab.getFds().getFd().add(tfd);
				else
					ttab.setFds(tfds);
			}
		}		
	}
	
	// Returns the type of the relation
	public Relations getRelType(Endpoints eps)
	{
		String func = "";
		String entityWithFk = "";
		for (Endpoint ep : eps.getEndpoint()) {
			if (func == "" && entityWithFk == "") {
				entityWithFk = ep.getEntity();
				func = ep.getFunc();
			} else {
				String newFunc = ep.getFunc();
				if (func.equals("n") && newFunc.equals("1")) {
					return Relations.NtoOne;
				} else if (func.equals("1") && newFunc.equals("n")) {
					return Relations.OnetoN;
				} else if (func.equals("n") && newFunc.equals("n"))	{
					return Relations.NtoN;
				} else if (func.equals("1") && newFunc.equals("1"))	{
					return Relations.OnetoOne;
				}
				func = "";
				entityWithFk = "";
			}
		}
		return Relations.Nothing;
	}
	
	// simply deletes a Table
	public void deleteTable(String name)
	{
		int index = 0;
		for(TTable table : rootElementTInfo.getTable())
		{
			if(table.getName().equals(name))
			{
				rootElementTInfo.getTable().remove(index);
				return;
			}
			index++;
		}
	}
	
	// Handles the One to One relation ship cases
	public void addOneToOneRel(Endpoints eps,RelationshipType rt){
		String karMinLeft = eps.getEndpoint().get(0).getKarMIN();
		String karMinRight = eps.getEndpoint().get(1).getKarMIN();
		// (0,1) : (0,1)
		if(karMinLeft.equals("0") && karMinRight.equals("0")){
			addNToNRel(eps, rt);
		}
		// (0,1) : (1,1)
		if(karMinLeft.equals("0") && karMinRight.equals("1")){
			addFKToTable(eps.getEndpoint().get(1).getEntity(), 
					eps.getEndpoint().get(0).getEntity(), 
					ii.getPrimaryKey(eps.getEndpoint().get(0).getEntity()),true);
		}
		// (1,1) : (0,1)
		if(karMinLeft.equals("1") && karMinRight.equals("0")){
			addFKToTable(eps.getEndpoint().get(0).getEntity(), 
					eps.getEndpoint().get(1).getEntity(), 
					ii.getPrimaryKey(eps.getEndpoint().get(1).getEntity()),true);
		}
		// (1,1) : (1,1)
		// in this case we have do destroy the two tables and create a new one which contains
		// all the informations
		if(karMinLeft.equals("1") && karMinRight.equals("1"))
		{
			List<Attribute> theNewAttributes = ii.getEntity(eps.getEndpoint().get(0).getEntity()).getAttributes().getAttribute();
			for(Attribute at : theNewAttributes){
				at.setName(eps.getEndpoint().get(0).getEntity()+"_"+at.getName());
			}
			List<Attribute> theNewAttributes2 = ii.getEntity(eps.getEndpoint().get(1).getEntity()).getAttributes().getAttribute();
			for(Attribute at : theNewAttributes2){
				at.setName(eps.getEndpoint().get(1).getEntity()+"_"+at.getName());
			}
			theNewAttributes.addAll(theNewAttributes2);
			List<Attribute> theNewAlternativeKey = ii.getPrimaryKey(eps.getEndpoint().get(1).getEntity());
			for(Attribute at : theNewAlternativeKey){
				at.setName(eps.getEndpoint().get(1).getEntity()+"_"+at.getName());
			}
			if(theNewAlternativeKey != null)
			{	
				List<Attribute> tmpList = ii.getAlternativeKeys(eps.getEndpoint().get(0).getEntity());
				if(tmpList != null){
					for(Attribute at : tmpList){
						at.setName(eps.getEndpoint().get(0).getEntity()+"_"+at.getName());
					}
					theNewAlternativeKey.addAll(tmpList);
				}
			}
			List<Attribute> thePrimaryKeys = ii.getPrimaryKey(eps.getEndpoint().get(0).getEntity());
			for(Attribute at : thePrimaryKeys){
				at.setName(eps.getEndpoint().get(0).getEntity()+"_"+at.getName());
			}
			addTable(eps.getEndpoint().get(0).getEntity()+eps.getEndpoint().get(1).getEntity(), 
					theNewAttributes,
					thePrimaryKeys, 
					theNewAlternativeKey);
			
			deleteTable(eps.getEndpoint().get(0).getEntity());
			deleteTable(eps.getEndpoint().get(1).getEntity());
		}
	}
	
	// Handles the One to N relation ship cases
	public void addOneToNRel(Endpoints eps,RelationshipType rt){
		// Turned because this are now cardinality not functionality
		String karMinLeft = eps.getEndpoint().get(0).getKarMIN();
		String karMinRight = eps.getEndpoint().get(1).getKarMIN();
		// (1,N) : (1:1)
		if(karMinLeft.equals("1") && karMinRight.equals("1")){
			addFKToTable(eps.getEndpoint().get(1).getEntity(), 
					eps.getEndpoint().get(0).getEntity(), 
					ii.getPrimaryKey(eps.getEndpoint().get(0).getEntity()),true);
		}
		// (1,N) : (0:1)
		if(karMinLeft.equals("1") && karMinRight.equals("0")){
			addNToNRel(eps, rt);
		}
		// (0,N) : (1:1)
		if(karMinLeft.equals("0") && karMinRight.equals("1")){
			addFKToTable(eps.getEndpoint().get(1).getEntity(), 
					eps.getEndpoint().get(0).getEntity(), 
					ii.getPrimaryKey(eps.getEndpoint().get(0).getEntity()),true);
		}
		// (0,N) : (0:1)
		if(karMinLeft.equals("0") && karMinRight.equals("0")){
			addNToNRel(eps, rt);
		}
	}

	// Handles the N to One relation ship cases
	public void addNToOneRel(Endpoints eps,RelationshipType rt){
		// Turned because this are now cardinality not functionality
		String karMinLeft = eps.getEndpoint().get(0).getKarMIN();
		String karMinRight = eps.getEndpoint().get(1).getKarMIN();
		// (1:1) : (1,N)
		if(karMinLeft.equals("1") && karMinRight.equals("1")){
			addFKToTable(eps.getEndpoint().get(0).getEntity(), 
					eps.getEndpoint().get(1).getEntity(), 
					ii.getPrimaryKey(eps.getEndpoint().get(1).getEntity()),true);
		}
		// (0:1) : (1,N)
		if(karMinLeft.equals("0") && karMinRight.equals("1")){
			addNToNRel(eps, rt);
		}
		// (1:1) : (0,N)
		if(karMinLeft.equals("1") && karMinRight.equals("0")){
			addFKToTable(eps.getEndpoint().get(0).getEntity(), 
					eps.getEndpoint().get(1).getEntity(), 
					ii.getPrimaryKey(eps.getEndpoint().get(1).getEntity()),true);
		}
		// (0:1) : (0,N)
		if(karMinLeft.equals("0") && karMinRight.equals("0")){
			addNToNRel(eps, rt);
		}
	}
	
	// This method creates the relation table between to tables with a N to N relation.
	public void addNToNRel(Endpoints eps,RelationshipType rt){
		List<Attribute> pk1 = ii.getPrimaryKey(eps.getEndpoint().get(0).getEntity());
		List<Attribute> pk2 = ii.getPrimaryKey(eps.getEndpoint().get(1).getEntity());
		List<String> pk3 = new ArrayList<String>();
		List<String> pk4 = new ArrayList<String>();

		// Set the Attributes to: Table_Attribute
		for(Attribute at1 : pk1){
			pk3.add(at1.getName());
			at1.setName(eps.getEndpoint().get(0).getEntity()+"_"+at1.getName());
		}	
		for(Attribute at1 : pk2){
			pk4.add(at1.getName());
			at1.setName(eps.getEndpoint().get(1).getEntity()+"_"+at1.getName());
		}
		List<Attribute> pk = new ArrayList<Attribute>();
		
		pk.addAll(pk1);
		pk.addAll(pk2);
		
		List<Attribute> ats = new ArrayList<Attribute>();
		ats.addAll(rt.getAttributes().getAttribute());
		
		// Create the relation table
		addTable(rt.getName(), ats, pk, null);
		
		List<Attribute> fkTab1 = new ArrayList<Attribute>();
		List<Attribute> fkTab2 = new ArrayList<Attribute>();
		
		for(String at : pk3){
			Attribute at_tmp = new Attribute();
			at_tmp.setName(at);
			fkTab1.add(at_tmp);
		}
		for(String at : pk4){
			Attribute at_tmp = new Attribute();
			at_tmp.setName(at);
			fkTab2.add(at_tmp);
		}
		
		// Add the foreign keys to the relation table
		addFKToTable(rt.getName(), eps.getEndpoint().get(0).getEntity(), fkTab1,true);
		addFKToTable(rt.getName(), eps.getEndpoint().get(1).getEntity(), fkTab2,true);
	}
	
	
	// if we have a non binary relation this method is called
	public void addRelationship(Endpoints eps,RelationshipType rt)
	{
		//Go through every table and collect all primary keys.
		//add the primary keys together as normal attributes
		List<Attribute> atrList = rt.getAttributes().getAttribute();
		List<Attribute> pkList = new ArrayList<Attribute>();
		List<Attribute> akList = new ArrayList<Attribute>();
		Attribute aPk = null;
		String aPk2 = "";
		for (Endpoint ep : eps.getEndpoint()) {
			String tableName = ep.getEntity();
			TTable et = getTable(tableName);
			TAttributeSet pks = et.getPrimaryKey();
			List<String> asd = pks.getAttribute();
			for(String at : asd)
			{
				Attribute atr = ii.getAttibute(ep.getEntity(), at);
				if(aPk2 == "")
					aPk2 = atr.getName();
				// Add the table name in front of the attribute name, important for all
				// mixed relation ship tables
				atr.setName(tableName+"_"+atr.getName());
				atrList.add(atr);
				// Set a primary key this is the fail save case if no endpoint have
				// a functionality n.
				if(aPk == null)
					aPk = atr;
				else
					akList.add(atr);
				// if the endpoint has the functionality n it is an primary key
				// so we add it to the primary key List
				if(ep.getFunc().equals("n"))
					pkList.add(atr);
			}
		}
		boolean addAk = false;
		if(pkList.size() == 0){
			pkList.add(aPk);
			addAk = true;
		}
		addTable(rt.getName(), atrList, pkList, null);
		if(addAk)
			for(Attribute at : akList)
				addAlternativKeyToTable(rt.getName(),at);
		for (Endpoint ep : eps.getEndpoint()) {
				addFKToTable2(rt.getName(), ep.getEntity(), getTable(ep.getEntity()).getPrimaryKey().getAttribute());
		}
	}
	
	public void addAlternativKeyToTable(String tabName, Attribute key)
	{
		TTable tab = getTable(tabName);
		TAlternateKeySet taks = tab.getAlternateKeys();
		if(taks == null){
			taks = new TAlternateKeySet();
			tab.setAlternateKeys(taks);
		}
		TAttributeSet tas = new TAttributeSet();
		tas.getAttribute().add(key.getName());
		taks.getAlternateKey().add(tas);
	}
	public void addAlternativKeyToTable(String tabName, List<Attribute> key)
	{
		getTable(tabName);
	}
	
	// get the TTable for the given name
	public TTable getTable(String name)
	{
		for(TTable tab : rootElementTInfo.getTable()){
			if(tab.getName().equals(name))
			{
				return tab;
			}
		}
		return null;
	}
}
